物理シミュレーションを使用してウィジェットをアニメーション化する
物理シミュレーションにより、アプリのインタラクションを現実的でインタラクティブなものにすることができます。 たとえば、ウィジェットをアニメーション化して、ウィジェットがアタッチされているかのように動作させることができます。 バネまたは重力による落下。
このレシピでは、ウィジェットをドラッグしたポイントから元のポイントに移動する方法を示します。 スプリングシミュレーションを使用して中心を合わせます。
このレシピでは次の手順を使用します。
- アニメーションコントローラーを設定する
- ジェスチャーを使用してウィジェットを移動する
- ウィジェットをアニメーション化する
- バネの動きをシミュレートする速度を計算します。
ステップ 1: アニメーション コントローラーをセットアップする
というステートフル ウィジェットから始めますDraggableCard
:
import 'package:flutter/material.dart';
void main() {
runApp(const MaterialApp(home: PhysicsCardDragDemo()));
}
class PhysicsCardDragDemo extends StatelessWidget {
const PhysicsCardDragDemo({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: const DraggableCard(
child: FlutterLogo(
size: 128,
),
),
);
}
}
class DraggableCard extends StatefulWidget {
const DraggableCard({required this.child, super.key});
final Widget child;
@override
State<DraggableCard> createState() => _DraggableCardState();
}
class _DraggableCardState extends State<DraggableCard> {
@override
void initState() {
super.initState();
}
@override
void dispose() {
super.dispose();
}
@override
Widget build(BuildContext context) {
return Align(
child: Card(
child: widget.child,
),
);
}
}
を作る_DraggableCardState
クラスの拡張元SingleTickerProviderStateMixin。
次に、アニメーションコントローラーのinitState
そしてセットvsync
にthis
。
@@ -29,14 +29,20 @@
|
|
29
29
|
State<DraggableCard> createState() => _DraggableCardState();
|
30
30
|
}
|
31
|
-
class _DraggableCardState extends State<DraggableCard>
|
31
|
+
class _DraggableCardState extends State<DraggableCard>
|
32
|
+
with SingleTickerProviderStateMixin {
|
33
|
+
後期AnimationController _controller;
|
34
|
+
|
32
35
|
@オーバーライド
|
33
36
|
void initState() {
|
34
37
|
super.initState();
|
38
|
+
_コントローラー =
|
39
|
+
AnimationController(vsync:this,duration:constDuration(秒:1));
|
35
40
|
}
|
36
41
|
@オーバーライド
|
37
42
|
void destroy() {
|
43
|
+
_controller.dispose();
|
38
44
|
super.dispose();
|
39
45
|
}
|
ステップ 2: ジェスチャーを使用してウィジェットを移動する
ウィジェットをドラッグすると移動するようにし、位置合わせフィールドに_DraggableCardState
クラス:
@@ -1,3 +1,4 @@
|
|
1
1
|
class _DraggableCardState extends State<DraggableCard>
|
2
2
|
with SingleTickerProviderStateMixin {
|
3
3
|
後期AnimationController _controller;
|
4
|
+
配置 _dragAlignment = Alignment.center;
|
追加ジェスチャー検出器を扱うのはonPanDown
、onPanUpdate
、 とonPanEnd
コールバック。アライメントを調整するには、メディアクエリを得るために
ウィジェットのサイズを求めて 2 で割ります (これにより、「ドラッグされたピクセル」の単位が に変換されます)
それをコーディネートします整列を使用します。)次に、Align
ウィジェットのalignment
に_dragAlignment
:
@@ -1,8 +1,22 @@
|
|
1
1
|
@オーバーライド
|
2
2
|
ウィジェットのビルド(BuildContext context) {
|
3
|
-
|
4
|
-
|
5
|
-
|
3
|
+
変数 サイズ = MediaQuery.of(コンテキスト).サイズ;
|
4
|
+
戻る ジェスチャー検出器(
|
5
|
+
パンダウン:(詳細) {}、
|
6
|
+
onPanUpdate: (詳細) {
|
7
|
+
setState(() {
|
8
|
+
_dragAlignment += 配置(
|
9
|
+
詳細.デルタ.dx / (サイズ.幅 / 2)、
|
10
|
+
詳細.delta.dy / (サイズ.高さ / 2),
|
11
|
+
);
|
12
|
+
});
|
13
|
+
}、
|
14
|
+
onPanEnd: (詳細) {}、
|
15
|
+
子: 整列(
|
16
|
+
配置: _dragAlignment、
|
17
|
+
子: カード(
|
18
|
+
子: widget.child、
|
19
|
+
)、
|
6
20
|
)、
|
7
21
|
);
|
8
22
|
}
|
ステップ 3: ウィジェットをアニメーション化する
ウィジェットを放すと、中央に戻るはずです。
を追加Animation<Alignment>
フィールドと_runAnimation
方法。これ
メソッドが定義するTween
ウィジェットがあったポイントの間を補間します
中央の点までドラッグします。
@@ -1,4 +1,5 @@
|
|
1
1
|
class _DraggableCardState extends State<DraggableCard>
|
2
2
|
with SingleTickerProviderStateMixin {
|
3
3
|
後期AnimationController _controller;
|
4
|
+
後期アニメーション<Alignment> _animation;
|
4
5
|
配置 _dragAlignment = Alignment.center;
|
void _runAnimation() {
_animation = _controller.drive(
AlignmentTween(
begin: _dragAlignment,
end: Alignment.center,
),
);
_controller.reset();
_controller.forward();
}
次にアップデート_dragAlignment
いつAnimationController
を生成します
価値:
@@ -3,4 +3,9 @@
|
|
3
3
|
super.initState();
|
4
4
|
_コントローラー =
|
5
5
|
AnimationController(vsync:this,duration:constDuration(秒:1));
|
6
|
+
_controller.addListener(() {
|
7
|
+
setState(() {
|
8
|
+
_dragAlignment = _animation.value;
|
9
|
+
});
|
10
|
+
});
|
6
11
|
}
|
次に、Align
ウィジェットを使用する_dragAlignment
分野:
child: Align(
alignment: _dragAlignment,
child: Card(
child: widget.child,
),
),
最後に、更新しますGestureDetector
アニメーション コントローラーを管理するには:
@@ -1,5 +1,7 @@
|
|
1
1
|
return GestureDetector(
|
2
|
-
onPanDown: (詳細) {
|
2
|
+
onPanDown: (詳細) {
|
3
|
+
_コントローラー.ストップ();
|
4
|
+
}、
|
3
5
|
onPanUpdate: (詳細) {
|
4
6
|
setState(() {
|
5
7
|
_dragAlignment += 配置(
|
@@ -8,7 +10,9 @@
|
|
8
10
|
);
|
9
11
|
});
|
10
12
|
}、
|
11
|
-
onPanEnd: (詳細) {
|
13
|
+
onPanEnd: (詳細) {
|
14
|
+
_runAnimation();
|
15
|
+
}、
|
12
16
|
子: 整列(
|
13
17
|
配置: _dragAlignment、
|
14
18
|
子: カード(
|
ステップ 4: バネの動きをシミュレートする速度を計算します。
最後のステップは、ウィジェットの速度を計算するために少し計算を行うことです。
ドラッグが終了した後。これはウィジェットを現実的にするためです
スナップバックされるまでその速度で続行します。 (_runAnimation
方法
アニメーションの開始位置と終了位置を設定することで、すでに方向が設定されています)。
まず、インポートしますphysics
パッケージ:
import 'package:flutter/physics.dart';
のonPanEnd
コールバックは、ドラッグエンド詳細物体。このオブジェクト
ポインタが画面との接触を停止したときのポインタの速度を示します。の
速度はピクセル/秒単位ですが、Align
ウィジェットはピクセルを使用しません。それ
[-1.0, -1.0] から [1.0, 1.0] までの座標値を使用します。ここで、[0.0, 0.0]
中心を表します。のsize
ステップ 2 で計算されたピクセルを変換するために使用されます
この範囲内の値を調整します。
ついに、AnimationController
持っていますanimateWith()
を与えることができるメソッド春シミュレーション:
/// Calculates and runs a [SpringSimulation].
void _runAnimation(Offset pixelsPerSecond, Size size) {
_animation = _controller.drive(
AlignmentTween(
begin: _dragAlignment,
end: Alignment.center,
),
);
// Calculate the velocity relative to the unit interval, [0,1],
// used by the animation controller.
final unitsPerSecondX = pixelsPerSecond.dx / size.width;
final unitsPerSecondY = pixelsPerSecond.dy / size.height;
final unitsPerSecond = Offset(unitsPerSecondX, unitsPerSecondY);
final unitVelocity = unitsPerSecond.distance;
const spring = SpringDescription(
mass: 30,
stiffness: 1,
damping: 1,
);
final simulation = SpringSimulation(spring, 0, 1, -unitVelocity);
_controller.animateWith(simulation);
}
忘れずに電話してください_runAnimation()
速度とサイズを指定すると、次のようになります。
onPanEnd: (details) {
_runAnimation(details.velocity.pixelsPerSecond, size);
},
インタラクティブな例
import 'package:flutter/material.dart';
import 'package:flutter/physics.dart';
void main() {
runApp(const MaterialApp(home: PhysicsCardDragDemo()));
}
class PhysicsCardDragDemo extends StatelessWidget {
const PhysicsCardDragDemo({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: const DraggableCard(
child: FlutterLogo(
size: 128,
),
),
);
}
}
/// A draggable card that moves back to [Alignment.center] when it's
/// released.
class DraggableCard extends StatefulWidget {
const DraggableCard({required this.child, super.key});
final Widget child;
@override
State<DraggableCard> createState() => _DraggableCardState();
}
class _DraggableCardState extends State<DraggableCard>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
/// The alignment of the card as it is dragged or being animated.
///
/// While the card is being dragged, this value is set to the values computed
/// in the GestureDetector onPanUpdate callback. If the animation is running,
/// this value is set to the value of the [_animation].
Alignment _dragAlignment = Alignment.center;
late Animation<Alignment> _animation;
/// Calculates and runs a [SpringSimulation].
void _runAnimation(Offset pixelsPerSecond, Size size) {
_animation = _controller.drive(
AlignmentTween(
begin: _dragAlignment,
end: Alignment.center,
),
);
// Calculate the velocity relative to the unit interval, [0,1],
// used by the animation controller.
final unitsPerSecondX = pixelsPerSecond.dx / size.width;
final unitsPerSecondY = pixelsPerSecond.dy / size.height;
final unitsPerSecond = Offset(unitsPerSecondX, unitsPerSecondY);
final unitVelocity = unitsPerSecond.distance;
const spring = SpringDescription(
mass: 30,
stiffness: 1,
damping: 1,
);
final simulation = SpringSimulation(spring, 0, 1, -unitVelocity);
_controller.animateWith(simulation);
}
@override
void initState() {
super.initState();
_controller = AnimationController(vsync: this);
_controller.addListener(() {
setState(() {
_dragAlignment = _animation.value;
});
});
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final size = MediaQuery.of(context).size;
return GestureDetector(
onPanDown: (details) {
_controller.stop();
},
onPanUpdate: (details) {
setState(() {
_dragAlignment += Alignment(
details.delta.dx / (size.width / 2),
details.delta.dy / (size.height / 2),
);
});
},
onPanEnd: (details) {
_runAnimation(details.velocity.pixelsPerSecond, size);
},
child: Align(
alignment: _dragAlignment,
child: Card(
child: widget.child,
),
),
);
}
}